using System;
using System.Threading;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Xml.Serialization;
using System.IO;
using System.Web.Script.Serialization;

namespace Derrick.Web
{
    public enum MessageFormat
    {
        text = 0,
        xml = 1,
        json = 2
    }

    public class Client
    {
        private TcpClient client = null;
        private NetworkStream stream = null;
        private AuthConfig authconfig;

        Server server;

        byte[] read_buffer = new byte[1024];
        string buffer = "";
        int bufferpos = 0;

        Request request;
        ParseState state = ParseState.Start;

        public bool IsLive = true;

        private TraceSwitch _traceSwitch;

        private DateTime _lastUpdate;

        public TcpClient TCPClient
        {
            get { return client; }
        }

        protected bool _isTcpClientClosed;
        private static Mutex _tcpClientMutex = new Mutex();

        public int ID { get; set; }
        public bool SendMessage { get; set; }

        private bool _authenticated;
        private MessageFormat _messageFormat;

        public Client(TcpClient c, Server s, AuthConfig a, int id)
        {
            _traceSwitch = new TraceSwitch("TraceLevelSwitch", "COMET Client.");
            ID = id;

            try
            {
                client = c;
                server = s;
                authconfig = a;

                bool tcpClientClosed = false;
                _tcpClientMutex.WaitOne();
                tcpClientClosed = _isTcpClientClosed;
                _tcpClientMutex.ReleaseMutex();
                if (tcpClientClosed)
                {
                    return;
                }
                stream = client.GetStream();

                stream.BeginRead(read_buffer, 0, 1024, new AsyncCallback(callbackRead), this);
            }
            catch (Exception ex)
            {
                Trace.WriteLineIf(_traceSwitch.TraceError, ex.ToString(), "COMET Client");
            }
        }

        public static void callbackRead(IAsyncResult ar)
        {
            TraceSwitch localTraceSwitch = new TraceSwitch("TraceLevelSwitch", "COMET client");
            Trace.WriteLineIf(localTraceSwitch.TraceVerbose, "Inside Client.callbackRead()...");

            Client dc = (Client)ar.AsyncState;
            int bytes = 0;

            try
            {

                bytes = dc.stream.EndRead(ar); //this was called when connection was closed!

                //if (bytes <= 0)
                //{
                //    dc.CleanUp();
                //    return;
                //}

                dc.buffer += Encoding.ASCII.GetString(dc.read_buffer, 0, bytes);

                //Console.WriteLine(dc.buffer);

                dc.ParseInput();

                if (dc.stream != null)
                {
                    dc.stream.BeginRead(dc.read_buffer, 0, 1024, new AsyncCallback(Client.callbackRead), dc);
                }
            }
            catch (Exception e)
            {
                //Console.WriteLine("Client: Error in callbackRead() - {0}", e.Message);
                Trace.WriteLineIf(localTraceSwitch.TraceError, e.ToString(), "Derrick COMET Client");
            }

            Trace.WriteLineIf(localTraceSwitch.TraceVerbose, "Exiting Client.callbackRead().");

        }

        private enum ParseState
        {
            Start,
            Headers,
            Content,
            Done
        }

        private void ParseInputOriginal()
        {
            int pos;
            string temp;
            bool skip = false;

            Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput()...");

            while (bufferpos < buffer.Length - 1 && state != ParseState.Done && !skip)
            {
                switch (state)
                {
                    case ParseState.Start:
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Start");
                        if ((pos = buffer.IndexOf("\r\n", bufferpos)) >= 0)
                        {
                            temp = buffer.Substring(bufferpos, pos - bufferpos);
                            bufferpos = pos + 2;

                            string[] parts = temp.Split(' ');
                            if (parts.Length == 3)
                            {
                                request = new Request();

                                request.Method = parts[0];
                                request.Url = parts[1];
                                request.Version = parts[2];

                                state = ParseState.Headers;
                            }
                            else
                            {
                                CleanUp();
                            }
                        }
                        break;
                    case ParseState.Headers:
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Headers");
                        if ((pos = buffer.IndexOf("\r\n", bufferpos)) >= 0)
                        {
                            temp = buffer.Substring(bufferpos, pos - bufferpos);
                            bufferpos = pos + 2;

                            if (temp.Length > 0)
                            {
                                string[] parts = temp.Split(new string[1] { ": " }, StringSplitOptions.RemoveEmptyEntries);
                                if (parts.Length == 2)
                                {
                                    request.Headers.Add(parts[0], parts[1]);
                                }
                            }
                            else
                            {
                                if (request.HasContent())
                                {
                                    state = ParseState.Content;
                                }
                                else
                                {
                                    state = ParseState.Done;
                                }
                            }
                        }
                        break;
                    case ParseState.Content:
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Content");
                        if (bufferpos + request.ContentLength <= buffer.Length)
                        {
                            int t = bufferpos + request.ContentLength - 1;
                            request.Body = buffer.Substring(bufferpos, request.ContentLength);
                            bufferpos += request.ContentLength;

                            state = ParseState.Done;
                        }
                        else
                        {
                            skip = true;
                        }
                        break;
                }
            }

            if (state == ParseState.Done)
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Done");
                state = ParseState.Start;

                //Response response;

                //if (request.VerifyAuth(authconfig))
                //{
                //    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): calling server.HandleRequest()");
                //    response = server.HandleRequest(request, _lastUpdate, this);
                //    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): Done server.HandleRequest()");
                //}
                //else
                //{
                //    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Client.ParseInput()...ParseState.Done but unauthorized.");
                //    response = Response.Get401Response(authconfig);
                //    SendMessage = true;
                //}

                //if (SendMessage)
                //{
                //    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Client.ParseInput()...Sending message to client now.");
                //    response.SendResponse(stream, this);
                //    _lastUpdate = response.LastUpdate;
                //}

                //request = null;

                if (request.VerifyAuth(authconfig))
                {
                    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "This client has been authenticated.");
                    _authenticated = true;
                }
                else
                {
                    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "This client failed authentication.");
                    Response response = Response.Get401Response(authconfig);
                    response.SendResponse(stream, this);
                }

            }
        }

        public void Send(string data)
        {
            byte[] bytes = Encoding.ASCII.GetBytes(data);
            int offset = 0;
            int len = 0;
            //Console.WriteLine("Sending: " + bytes.Length);
            while (offset < bytes.Length)
            {
                offset = Math.Min(offset, bytes.Length - 1);
                len = Math.Min(1024, bytes.Length - offset);

                stream.Write(bytes, offset, len);

                offset += 1024;
            }
            //Console.WriteLine("Done.");
        }

        public void CleanUp()
        {

            try
            {
                stream.Close();
            }
            catch (Exception ex)
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Error in Client.CleanUp() on stream.Close(): " + ex.ToString());
            }
            try
            {
                stream.Dispose();
            }
            catch (Exception ex)
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Error in Client.CleanUp() on stream.Dispose(): " + ex.ToString());
            }
            try
            {
                _tcpClientMutex.WaitOne();
                _isTcpClientClosed = true;
                client.Close();
                _tcpClientMutex.ReleaseMutex();
            }
            catch (Exception ex)
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Error in Client.CleanUp() on client.CLose(): " + ex.ToString());
            }

            stream = null;
            client = null;

            IsLive = false;
        }

        public void SendResponse(SendMessageEventArgs args)
        {
            if (args == null || args.Timestamp == null)
            {
                return;
            }
            if (_lastUpdate > args.Timestamp)
            {
                return;
            }

            if (request.HasContent())
            {
                JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
                string jsonMessageToSend;
                Response response;

                if (_messageFormat == MessageFormat.json)
                {
                    ClientMessage2 jsonObjectToSend = new ClientMessage2();
                    jsonObjectToSend.SendTimestamp = args.Timestamp.ToString("MMMM dd, yyyy HH:mm:ss");
                    jsonObjectToSend.Message = args.Message;
                    jsonMessageToSend = jsonSerializer.Serialize(jsonObjectToSend);
                    response = Response.GetHtmlResponse(jsonMessageToSend, args.Timestamp, _messageFormat);
                    response.SendResponse(stream, this);
                }

                _lastUpdate = args.Timestamp;
            }
            else
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "The client request is missing required info.");
            }
        }

        public void ParseRequestContent()
        {
            if (_traceSwitch == null)
            {
                _traceSwitch = new TraceSwitch("TraceLevelSwitch", "Comet Tracing");
            }

            Trace.WriteLineIf(_traceSwitch.TraceInfo, "Inside Client.ParseRequestContent()...");

            try
            {

                MemoryStream mem = new MemoryStream();
                mem.Write(System.Text.Encoding.ASCII.GetBytes(request.Body), 0, request.Body.Length);
                mem.Seek(0, 0);

                if (request.Headers["Content-Type"].Contains("xml"))
                {
                    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Received XML content from client.");
                    _messageFormat = MessageFormat.xml;

                    #region Process HTTP message as XML

                    try
                    {
                        //Picks up message from HTTP:
                        XmlSerializer s = new XmlSerializer(typeof(Derrick.Web.SIServer.SIRequest));

                        //Loads message into object for processing.
                        Derrick.Web.SIServer.SIRequest data = (Derrick.Web.SIServer.SIRequest)s.Deserialize(mem);
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "During parse of client XML request got this exception: " + ex.ToString());
                    }

                    #endregion Process HTTP message as XML

                }
                else if (request.Headers["Content-Type"].Contains("json"))
                {
                    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Received json content from client.");
                    _messageFormat = MessageFormat.json;

                    #region Process HTTP message as JSON

                    try
                    {
                        JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
                        ClientMessage1 clientMessage = jsonSerializer.Deserialize<ClientMessage1>(request.Body);
                        _lastUpdate = clientMessage.SendTimestamp;
                    }
                    catch (Exception ex)
                    {
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Error deserializing JSON message: " + ex.ToString());
                    }

                    #endregion Process HTTP message as JSON

                }


            }
            catch (Exception ex)
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Client.ParseRequestContent() general exception: " + ex.ToString());
            }

        }

        private void ParseInput()
        {
            int pos;
            string temp;
            bool skip = false;

            Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput()...");

            while (bufferpos < buffer.Length - 1 && state != ParseState.Done && !skip)
            {
                switch (state)
                {
                    case ParseState.Start:
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Start");
                        if ((pos = buffer.IndexOf("\r\n", bufferpos)) >= 0)
                        {
                            temp = buffer.Substring(bufferpos, pos - bufferpos);
                            bufferpos = pos + 2;

                            string[] parts = temp.Split(' ');
                            if (parts.Length == 3)
                            {
                                request = new Request();

                                request.Method = parts[0];
                                request.Url = parts[1];
                                request.Version = parts[2];

                                state = ParseState.Headers;
                            }
                            else
                            {
                                CleanUp();
                            }
                        }
                        break;
                    case ParseState.Headers:
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Headers");
                        if ((pos = buffer.IndexOf("\r\n", bufferpos)) >= 0)
                        {
                            temp = buffer.Substring(bufferpos, pos - bufferpos);
                            bufferpos = pos + 2;

                            if (temp.Length > 0)
                            {
                                string[] parts = temp.Split(new string[1] { ": " }, StringSplitOptions.RemoveEmptyEntries);
                                if (parts.Length == 2)
                                {
                                    request.Headers.Add(parts[0], parts[1]);
                                }
                            }
                            else
                            {
                                if (request.HasContent())
                                {
                                    state = ParseState.Content;
                                }
                                else
                                {
                                    state = ParseState.Done;
                                }
                            }
                        }
                        break;
                    case ParseState.Content:
                        Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Content");
                        if (bufferpos + request.ContentLength <= buffer.Length)
                        {
                            int t = bufferpos + request.ContentLength - 1;
                            request.Body = buffer.Substring(bufferpos, request.ContentLength);
                            bufferpos += request.ContentLength;

                            state = ParseState.Done;
                        }
                        else
                        {
                            skip = true;
                        }
                        break;
                }
            }

            if (state == ParseState.Done)
            {
                Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Inside ParseInput(): ParseState.Done");
                state = ParseState.Start;
                ParseRequestContent();

                if (request.VerifyAuth(authconfig))
                {
                    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "This client has been authenticated.");
                    _authenticated = true;
                }
                else
                {
                    Trace.WriteLineIf(_traceSwitch.TraceVerbose, "This client failed authentication.");
                    Response response = Response.Get401Response(authconfig);
                    response.SendResponse(stream, this);
                }

            }

            Trace.WriteLineIf(_traceSwitch.TraceVerbose, "Leaving Client.ParseInput().");

        }


    }
}


